"""
获取所有得市场对应得depth ,判断是否正常
如果异常得话则 做市场恢复

恢复市场得key : [exchange]:[xt-Market]:行情

"""

import redis
import json  # NOQA
import time  # NOQA
import requests  # NOQA
import multiprocessing  # NOQA
from copy import deepcopy
from log import logger
from constant import *


class Plat:

    """
    """
    name = BASE_EXCHANGE_NAME
    key = BASE_EXCHANGE_MARKETS_KEY
    ex_key = BASE_EXCHANGE_MARKETMATCH_KEY
    config_key = BASE_EXCHANGE_MARKETSCONFIG_KEY

    def request(self, method, url):

        count = 3
        time.sleep(2)
        while count > 0:
            try:
                res = requests.request(method, url)
                res.raise_for_status()

            except Exception as e:
                msg = "[get market list error .....] {}".format(e)
                logger.info(msg)
                count -= 1
            else:
                return res

    def parse_data(self, res):
        data = res.json()
        return data


class SubscribeMarket:

    # Redis 中对应平台匹配得市场 | zset数据结构
    key = SUSCRIBEMAREKET_EXCHANGE_KEYS

    def __init__(self):

        # 需要主力这里修改list 会影响markets
        self.con = redis.StrictRedis(host=LOCAL_HOST, port=LOCAL_PROT)

    def get_markets(self, palt):

        key = self.key.format(exchange=palt.pattern)
        data = self.con.zrange(key, 0, -1)
        return map(lambda x:  x.decode("utf-8") if isinstance(x, bytes) else x, data)


class Monitor:
    """
    获取所有平台对应得depth 时间戳对比是否满足条件
    这里只获取一个depth即可 判断是否正常

    ::param : markets XT 对应所有市场
    ::param : con Redis得连接数
    """

    during = INTERVAL  # 超时时间 重新订阅

    def __init__(self, exchange):

        # 需要主力这里修改list 会影响markets
        self.exchange = exchange()
        self.con = redis.StrictRedis(host=LOCAL_HOST, port=LOCAL_PROT)

    def loop(self, market, recover_key, data_key):
        """
        recover_key : exchange:makrets:recover
        """
        # 每次获取最新时间
        now = time.time() * 1000
        dataDict, count = self.exchange.get_ts(recover_key, market, data_key)
        if not dataDict and count <= 3:
            return

        if not dataDict:
            self.con.zadd(recover_key, {market: 1})
            msg = "市场获取失败>>>> {} {}".format(market, count)
            logger.info(msg)
            return
        else:
            ts = int(dataDict["ts"])
            time_diff = (now - ts) / 1000

            if time_diff > self.during:
                msg = "{} 时差 秒 ::: {} {}".format(
                    market, time_diff, time_diff > self.during)
                logger.info(msg)
                self.con.zadd(recover_key, {market: 1})

        time.sleep(0.5)
        # logger.info("[+] {} 市场正常....".format(market))
        # logger.info("[+]  key: {} | {} 市场正常....\n".format(data_key, market))

    def fetch_all(self):

        sub = SubscribeMarket()
        markets = sub.get_markets(self.exchange)

        for market in markets:
            # 跳过黑名单市场
            if market in self.exchange.blacklist:
                continue

            recover_key = self.exchange.recover_key(market)

            market = market.decode() if isinstance(market, bytes) else market
            key = self.exchange.get_key(market)
            # 协程启动
            self.loop(market, recover_key, key)

    def start(self):

        while 1:
            self.fetch_all()
            time.sleep(20)
            logger.info("[{}] 循环结束 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ".format(
                self.exchange.pattern))


class Base:
    """ """
    blacklist = BASE_BLACKLIST
    pattern = BASE_PATTERN
    key = BASE_PATTERN_KEY

    def __init__(self) -> None:
        """
        ::param conn : redis-conn
        """
        self._conn = redis.StrictRedis(host=LOCAL_HOST, port=LOCAL_PROT)

    def get_recover_keys(self):
        """
        获取所有binance实例的key
        """
        keys = []
        bkeys = self._conn.smembers(COMMON_SUB_KEYS)

        for bk in bkeys:
            if isinstance(bk, bytes):
                bk = bk.decode("utf-8")
            if self.pattern in bk:
                keys.append(bk)

        return keys

    def get_sub_keys(self, keys):
        """ """
        sub_keys = deepcopy(keys)
        sub_keys = list(map(lambda x: x.replace("recover", "sub"), sub_keys))
        return sub_keys

    def get_sub_key(self, keys, symbol):
        """ """
        for key in keys:
            symbols = list(map(lambda x: x.decode(
                "utf-8") if isinstance(x, bytes) else x, self._conn.smembers(key)))
            if symbol in symbols:
                return key.replace("sub", "recover")

    def recover_key(self, symbol):
        """
        获取对应市场恢复的key
        1. 首先判断市场属于的key
        2. 然后置换成恢复的key
        """
        keys = self.get_recover_keys()
        sub_keys = self.get_sub_keys(keys)
        key = self.get_sub_key(sub_keys, symbol)
        return key

    def get_key(self, market):
        key = self.key.format(
            exchange=self.pattern, market=market)
        return key

    def get_ts(self, recover_key, market, data_key):
        """ """
        count = 0

        data = self._conn.get(data_key)
        while count < 5:
            if not data:
                count += 1
                time.sleep(3)
            else:
                break

        if not data:
            return None, count

        try:
            dataDict = json.loads(data.decode("utf-8"))
        except AttributeError:
            return None, count
        # 获取redis里面得时间做对比
        # logger.info("dataDict", recover_key, market)
        return dataDict, count


class Binance(Base):
    blacklist = BINANCE_BLACKLIST
    pattern = BINANCE_PATTERN


class FTX(Base):
    """ """
    pattern = FTX_PATTERN
    blacklist = FTX_BLACKLIST

    def get_ts(self, recover_key, market, data_key):
        dataDict, count = super().get_ts(recover_key, market, data_key)
        if not dataDict:
            return dataDict, count
        dataDict["ts"] = int(dataDict["ts"]) * 1000
        return dataDict, count


class XTFuture(Base):
    """ """

    pattern = XTFUTURE_PATTERN
    key = XTFUTURE_PATTERN_KEY
    blacklist = XTFUTURE_BLACKLIST

    def __init__(self) -> None:
        """
        ::param conn : redis-conn
        """
        self._conn = redis.StrictRedis(host=LOCAL_HOST, port=LOCAL_PROT, db=2)

    def get_ts(self, recover_key, market, data_key):
        """ """
        count = 0

        data = self._conn.get(data_key)

        while count < 5:
            if not data:
                count += 1
                time.sleep(3)
            else:
                break

            time.sleep(3)
        if not data:
            return None, count

        try:
            dataDict = json.loads(data.decode("utf-8"))
        except AttributeError:
            return None, count
        # 获取redis里面得时间做对比
        # logger.info("dataDict", recover_key, market)
        dataDict["ts"] = dataDict["data"]["t"]
        return dataDict, count


class XTFutureUser(Base):
    """ """
    pattern = XTFUTUREUSER_PATTERN
    key = XTFUTUREUSER_PATTERN_KEY
    blacklist = XTFUTUREUSER_BLACKLIST

    def get_key(self, market):
        """ """
        userkey = json.loads(market)
        key = self.key.format(
            exchange=self.pattern, market=userkey["accesskey"])
        return key

    def get_ts(self, recover_key, market, data_key):
        """ """
        count = 0
        data = self._conn.get(data_key)
        dataDict = dict()

        while count < 5:
            if not data:
                count += 1
                time.sleep(3)
            else:
                break

        if not data:
            return None, count

        try:
            dataDict["ts"] = int(data)
        except AttributeError:
            return None, count
        # 获取redis里面得时间做对比
        # logger.info("dataDict", recover_key, market)
        return dataDict, count


def multi_start():

    plats = [Binance, FTX, XTFuture, XTFutureUser]
    # plats = ["binance"]
    pool = []

    pool = multiprocessing.Pool(processes=3)
    logger.info("init")
    for plat in plats[:]:
        logger.info("初始化>>>>>>  {}".format(plat.pattern))
        monitor = Monitor(plat)
        pool.apply_async(monitor.start)

    while True:
        time.sleep(100)

    pool.close()
    pool.join()


def test_start():

    plats = [Binance]
    logger.info("init")
    for plat in plats[:]:
        logger.info("初始化>>>>>>  {}".format(plat.pattern))
        monitor = Monitor(plat)
        monitor.start()


if __name__ == "__main__":

    multi_start()
